
#include "ASpriteModel.h"
#include "AWorld.h"
#include "AXML.h"
#include "AString.h"
#include "ACache.h"

static const char* ATTR_Time = "time";
static const char* ATTR_AnchorPos = "apos";
static const char* ATTR_Position = "pos";
static const char* ATTR_ToPosition = "topos";
static const char* ATTR_YardPos = "yardpos";
static const char* ATTR_Scale = "scale";
static const char* ATTR_ToScale = "toscale";
static const char* ATTR_YardScale = "yardscale";
static const char* ATTR_YardScaleX = "yardscalex";
static const char* ATTR_YardScaleY = "yardscaley";
static const char* ATTR_Rotation = "rot";
static const char* ATTR_ToRotation = "torot";
static const char* ATTR_Alpha = "alpha";
static const char* ATTR_Name = "name";
static const char* ATTR_Part = "part";
static const char* ATTR_Abslutely = "abs";

void CASpriteModelPoseModifierKey::initialize(CAXMLNode* pnode)
{
	CCPoint pos;
	CCSize scale;
	float temp;
	CAString str;

	modifier = PM_NONE;

	//pm = (EPoseModifier)(pm | PM_SKEW);

	if (pnode->hasAttr(ATTR_Time))
	{
		CAStringUtils::toFloat(pnode->attr(ATTR_Time), time);
		time /= 1000.0f; //msecs to secones
	}
	else
	{
		time = 0;
	}

	if (pnode->hasAttr(ATTR_AnchorPos))
	{
		str = pnode->attr(ATTR_AnchorPos);
		CAStringUtils::toPoint(str, pos);
		ax = pos.x;
		ay = pos.y;
		ModifierOr(PM_APOS);	
	}

	if (pnode->hasAttr(ATTR_Position))
	{
		str = pnode->attr(ATTR_Position);
		CAStringUtils::toPoint(str, pos);
		x = pos.x; 
		y = pos.y;
		ModifierOr(PM_POS);	
	}

	/*
	if (pnode->hasAttr(ATTR_ToPosition))
	{
		str = pnode->attr(ATTR_Position);
		CAStringUtils::toPoint(str, pos);
		x = pos.x; 
		y = pos.y;
		ModifierOr(PM_TOPOS);	
	}
	*/
	
	if (pnode->hasAttr(ATTR_YardPos))
	{
		str = pnode->attr(ATTR_YardPos);
		//assume this is a percent pos
		CAStringUtils::toPoint(str, pos);

		x = pos.x; 
		y = pos.y;
		ModifierOr(PM_YARDPOS);	
	}

	if (pnode->hasAttr(ATTR_Scale))
	{
		str = pnode->attr(ATTR_Scale);
		CAStringUtils::toPoint(str, pos);
		scalex = pos.x; scaley = pos.y;
		ModifierOr(PM_SCALE);
	}

	/*
	if (pnode->hasAttr(ATTR_ToScale))
	{
		str = pnode->attr(ATTR_Scale);
		CAStringUtils::toPoint(str, pos);
		scalex = pos.x; scaley = pos.y;
		ModifierOr(PM_TOSCALE);
	}
	*/

	if (pnode->hasAttr(ATTR_YardScale))
	{
		str = pnode->attr(ATTR_YardScale);
		CAStringUtils::toPoint(str, pos);

		scalex = pos.x; scaley = pos.y;
		ModifierOr(PM_YARDSCALE);
	}

	if (pnode->hasAttr(ATTR_YardScaleX))
	{
		str = pnode->attr(ATTR_YardScaleX);
		CAStringUtils::toFloat(str, temp);
		scalex = scaley = temp; //scaley will be recalculated
		ModifierOr(PM_YARDSCALEX);
	}

	if (pnode->hasAttr(ATTR_YardScaleY))
	{
		str = pnode->attr(ATTR_YardScaleY);
		CAStringUtils::toFloat(str, temp);

		scalex = scaley = temp; //scalex will be recalculated
		ModifierOr(PM_YARDSCALEY);
	}

	if (pnode->hasAttr(ATTR_Rotation))
	{
		CAStringUtils::toFloat(pnode->attr(ATTR_Rotation), rot);
		ModifierOr(PM_ROT);
	}

	/*
	if (pnode->hasAttr(ATTR_ToRotation))
	{
		CAStringUtils::toFloat(pnode->attr(ATTR_Rotation), rot);
		ModifierOr(PM_TOROT);
	}
	*/

	if (pnode->hasAttr(ATTR_Alpha))
	{
		CAStringUtils::toFloat(pnode->attr(ATTR_Alpha), alpha);
		ModifierOr(PM_ALPHA);
	}
}


void CASpriteModelPoseModifierKey::lerp(
	CAFixArray<CASpriteModelPoseModifierKey>& keys, float time, CCPoint& curpos, CASpriteModelPoseModifierKey& result)
{
	int kc = keys.count();
	if (0 == kc) 
	{
		return;
	}
	else if (1 == kc) 
	{
		result = keys[0];
		return;
	}

	unsigned int i;
	CASpriteModelPoseModifierKey* k1 = &keys[0];
	CASpriteModelPoseModifierKey* k2 = &keys[1];
	EPoseModifierKeyAs kas = keys[0].keyas;
	if (KEYAS_TIME == kas)
	{
		for (i = 0; i < keys.count() - 1; i++)
		{
			k1 = &keys[i];
			k2 = &keys[i + 1];
			if (time >= k1->time && time < k2->time)
			{
				break;
			}
		}
	}
	else if (KEYAS_POSX == kas)
	{
		for (i = 0; i < keys.count() - 1; i++)
		{
			k1 = &keys[i];
			k2 = &keys[i + 1];
			_Assert(k1->keyas == KEYAS_POSX);
			_Assert(k2->keyas == KEYAS_POSX);
			_Assert(k1->canModify(PM_POS) || k1->canModify(PM_YARDPOS));
			_Assert(k2->canModify(PM_POS) || k2->canModify(PM_YARDPOS));
			if (curpos.x >= k1->x && curpos.x < k2->x)
			{
				break;
			}
		}
		if (curpos.x > k2->x) 
		{
			time = k2->time;
		}
		else if (curpos.x < k1->x) 
		{
			time = k1->time;
		}
		else
		{
			time = PERCENT(curpos.x, k1->x, k2->x);
			time = time * (k2->time - k1->time) + k1->time;
		}
	}
	else if (KEYAS_POSY == kas)
	{
		for (i = 0; i < keys.count() - 1; i++)
		{
			k1 = &keys[i];
			k2 = &keys[i + 1];
			_Assert(k1->keyas == KEYAS_POSY);
			_Assert(k2->keyas == KEYAS_POSY);
			_Assert(k1->canModify(PM_POS) || k1->canModify(PM_YARDPOS));
			_Assert(k2->canModify(PM_POS) || k2->canModify(PM_YARDPOS));
			if (curpos.y >= k1->y && curpos.y < k2->y)
			{
				break;
			}
		}
		if (curpos.y > k2->y) 
		{
			time = k2->time;
		}
		else if (curpos.y < k1->y) 
		{
			time = k1->time;
		}
		else
		{
			time = PERCENT(curpos.y, k1->y, k2->y);
			time = time * (k2->time - k1->time) + k1->time;
		}
	}
	else
	{
		_Assert(false);
	}
	_Assert(time >= k1->time);
	_Assert(time <= k2->time);
	_Assert(k1->time != k2->time);

	float fac = (float)(time - k1->time) / (float)(k2->time - k1->time);

#define _LERP(field) result.field = fac * (k2->field - k1->field) + k1->field
	_LERP(ax);
	_LERP(ay);
	_LERP(x);
	_LERP(y);
	_LERP(scalex);
	_LERP(scaley);
	_LERP(rot);
	_LERP(alpha);
#undef _LERP
	_Assert(k1->modifier == k2->modifier);
	//_Assert(k1->bPercentScale == k2->bPercentScale);
	if (time - k1->time < k2->time - time)
	{
		result.modifier = k1->modifier;
	}
	else
	{
		result.modifier = k2->modifier;
	}
}

void CASpriteModelPoseModifierKey::combine(
	const CASpriteModelPoseModifierKey& _gameKey, 
	CASpriteModelPoseModifierKey& _combinedKey)
{
	CASpriteModelPoseModifierKey& lerpKey = *this;

	//apos
	if (_gameKey.canModify(PM_APOS))
	{
		_combinedKey.ax = _gameKey.ax;
		_combinedKey.ay = _gameKey.ay;
	}
	else if (lerpKey.canModify(PM_APOS)) 
	{
		_combinedKey.ax = lerpKey.ax;
		_combinedKey.ay = lerpKey.ay;
	}
	else
	{
		_combinedKey.ax = 0.5f;
		_combinedKey.ay = 0.5f;
	}

	//position
	CCPoint pos = CCPointZero;
	if (lerpKey.canModify(PM_POS))// || lerpKey.canModify(PM_YARDPOS))
	{
		pos.x = lerpKey.x;
		pos.y = lerpKey.y;
	}
	_combinedKey.x = pos.x;
	_combinedKey.y = pos.y;
	_combinedKey.x += _gameKey.x;
	_combinedKey.y += _gameKey.y;

	//scale
	float sx, sy;
	sx = sy = 1.0f;
	if (lerpKey.canModify(PM_SCALE))// || lerpKey.canModify(PM_YARDSCALE))
	{
		sx = lerpKey.scalex;
		sy = lerpKey.scaley;
	}
	sx *= _gameKey.scalex;
	sy *= _gameKey.scaley;
	_combinedKey.scalex = sx;
	_combinedKey.scaley = sy;

	//rotation
	float fval = 0;
	if (lerpKey.canModify(PM_ROT))
	{
		fval = lerpKey.rot;
	}
	_combinedKey.rot = _gameKey.rot + fval;

	//alpha
	fval = 1.0f;
	if (lerpKey.canModify(PM_ALPHA))
	{
		fval = lerpKey.alpha;
	}
	fval *= _gameKey.alpha; 
	if (fval < 0) fval = 0;
	else if (fval > 1.0f) fval = 1.0f;
	_combinedKey.alpha = fval;
}

void CASpriteModelPose::_initializeModifier(CAXMLNode* pnode)
{
	GUARD_FUNCTION();

	_mode = String2AnimateMode(pnode->attr("mode"));
	float timescale = 1.0f;
	if (pnode->hasAttr("timescale"))
	{
		CAStringUtils::toFloat(pnode->attr("timescale"), timescale);
	}
	CAString keyas;
	if (pnode->hasAttr("keyas"))
	{
		//keyas can be: time, posx, posy
		keyas = pnode->attr("keyas");
	}
	else
	{
		keyas = "time";
	}
	EPoseModifierKeyAs nks;
	if (keyas == "posx") nks = KEYAS_POSX;
	else if (keyas == "posy") nks = KEYAS_POSY;
	else nks = KEYAS_TIME;

	float timeoffset = 0.0f;
	if (pnode->hasAttr("timeoffset"))
	{
		CAStringUtils::toFloat(pnode->attr("timeoffset"), timeoffset);
	}
	_timeOffset = timeoffset;

	if (pnode->hasAttr("ref"))
	{
		GUARD_FIELD(ref_modifier);
		CAString ref = pnode->attr("ref");
		CAXMLNode* pxml = CAXML::parse(ref.c_str(), true);
		pnode = pxml->firstnode("anim");
		_Assert(pnode);
		//!!!! pnode has switched to new XML file
	}

	CAXMLNodes* pnodes = pnode->nodes("key");
	int ks = 0;
	if (null != pnodes)
		ks = pnodes->count();
	_keys.init(ks);

	CCObject* pkey;
	int index = 0;
	_durationModifier = 0;

	EPoseModifier pm = PM_NONE;
	CCARRAY_FOREACH(pnodes, pkey)
	{
		CASpriteModelPoseModifierKey& k = _keys[index];
		k.initialize((CAXMLNode*)pkey);
		k.time *= timescale;
		k.keyas = nks;
		if (k.time > _durationModifier)
		{
			_durationModifier = k.time;
		}
		if (0 == index)
		{
			pm = k.modifier;
		}
		else
		{
			_Assert(pm == k.modifier);
		}
		index++;
	}
	_Assert(_durationModifier >= 0.0f);

	//_Trace("pose %s:", _name.c_str());
	//for (index = 0; index < ks; index++)
	{
		//CASpriteModelPoseModifierKey& k = _keys[index];
		//_Trace("%d time=%f x=%f y=%f,du=%f", index, k.time, k.x, k.y, _durationModifier);
	}
}

void CASpriteModelPose::_initializePart(CAXMLNode* pnode)
{
	GUARD_FUNCTION();

	CAXMLNode* pnodeMesh = pnode->firstnode("mesh");
	if (null == pnodeMesh)
	{
		_mesh = "";
		_meshAnimDuration = 1.0f * 0x3fffffff; //for safe
	}
	else
	{
		_mesh = pnodeMesh->attr("ref"); //this can be ""
		CAStringUtils::toFloat(pnodeMesh->attr("dt"), this->_meshAnimDuration);
	}

	//get mesh and duration
	pnode = pnode->firstnode("modifier");
	_initializeModifier(pnode);
}

#if 0
bool CASpriteModelPose::lerp(float time, CASpriteModelPoseModifierKey& key)
{
	_Assert(false);
	size_t i;

	//_Assert(time <= _duration); may be _keys.count <= 1

	switch(_keys.count())
	{
	case 0:
		break;
	case 1:
		key = _keys[0];
		//CASpriteModelPoseModifierKey::lerp(_keys[0].time, _keys[0], _keys[0], key);
		break;
	default:
		{
			CASpriteModelPoseModifierKey* k1 = &_keys[0];
			CASpriteModelPoseModifierKey* k2 = &_keys[1];
			for (i = 0; i < _keys.count() - 1; i++)
			{
				k1 = &_keys[i];
				k2 = &_keys[i + 1];
				if (time >= _keys[i].time && time < _keys[i + 1].time)
				{
					break;
				}
			}
			CASpriteModelPoseModifierKey::lerp(time, *k1, *k2, key);
		}
		break;
	}

	return true;
}
#endif

CCSpriteFrame* CASpriteModelPose::lerpFrame(float time, CCSpriteFrame* psfDisplayed)
{
	if (_mesh.length() > 0)
	{
		//CCMutableArray<CCSpriteFrame*>* frames = 
		if (_mesh == "{last}")
			return psfDisplayed;

		CCArray* frames = 
			CAWorld::framesByAnimationName(_mesh.c_str());

		int fc = (int)frames->count();
		int frame = (int)((float)fc * (float)fmod(time, _meshAnimDuration) / _meshAnimDuration);
		if (frame >= fc) frame %= fc;
		_Assert(frame >= 0 && frame < (int)frames->count());

		CCAnimationFrame* pf = (CCAnimationFrame*)frames->objectAtIndex(frame);
		return pf->getSpriteFrame();
	}

	return null;
}


CASpriteModelPose::CASpriteModelPose()
{
	_collid.width = _collid.height = 1.0f;
	_mode = AM_CLAMP;
	_meshAnimDuration = 0.0f;

	_durationModifier = 0.0f;
	_timeOffset = 0.0f;
}

CASpriteModelPose::~CASpriteModelPose()
{
}

//<pose name="burn" anim="player_swim">
void CASpriteModelPose::initialize(CAXMLNode* pnode)
{
	GUARD_FUNCTION();

	_name = pnode->attr(ATTR_Name);
	_Assert(_name.length() > 0);
	if (pnode->hasAttr("collid"))
	{
		CAStringUtils::toSize(pnode->attr("collid"), _collid);
	}

	CAXMLNode* pnodeBone = pnode->firstnode(ATTR_Part);
	_initializePart(pnodeBone);

	return;
}

const char* CACHE_MODEL = "model";
CASpriteModel::CASpriteModel()
{
	GUARD_FUNCTION();

	autorelease();

	_poses = new CAStringMap<CASpriteModelPose>();
}

CASpriteModel::~CASpriteModel(void)
{
	_Assert(_poses);
	_poses->release();
	_poses = null;
	//_Trace("Sprite Model released");
}

void CASpriteModel::initialize(CAXMLNode* pnode)
{
	GUARD_FUNCTION();

	//get version
	_version = pnode->attr("version");

	//load texture
	CAXMLNode* pnodeTexture = pnode->firstnode("texture");
	CAString list = pnodeTexture->attr("src");
	CAString tex = pnodeTexture->attr("tex");
	_Assert(list.length() > 0);
	_Assert(tex.length() > 0);

	//install texture
	CCSpriteFrameCache::sharedSpriteFrameCache()->
		addSpriteFramesWithFile(RES(list.c_str()), RES(tex.c_str()));

	//load submesh 
	CCObject* pnodeobj;
	CAXMLNodes* pnodes = pnode->nodes("mesh");
	if (null != pnodes)
	{
		CCARRAY_FOREACH(pnodes, pnodeobj)
		{
			GUARD_FIELD(loadframes);

			CAXMLNode* psub = (CAXMLNode*)pnodeobj;
			CAString name = psub->attr(ATTR_Name);	
			_Assert(name.length() > 0);
			if (CCAnimationCache::sharedAnimationCache()->animationByName(name.c_str()))
			{
				_Trace("mesh[%s] exist!", name.c_str());
			}
			else
			{
				CAXMLNodes* pframeNodeNodes = psub->nodes("frame");
				//CCMutableArray<CCSpriteFrame*>* frames = 
				//	new CCMutableArray<CCSpriteFrame*>(pframeNodeNodes->count());
				CCArray* frames = CCArray::arrayWithCapacity(pframeNodeNodes->count());

				CCObject* pframeNode;
				CCARRAY_FOREACH(pframeNodeNodes, pframeNode)
				{
					CAString frame = ((CAXMLNode*)pframeNode)->attr("src");
					CCSpriteFrame* psf = CAWorld::spriteFrameByName(frame.c_str());
					_Assert(psf);
					frames->addObject(psf);
				}
				//CCAnimation* panim = CCAnimation::animationWithFrames(frames);
				CCAnimation* panim = CCAnimation::animationWithSpriteFrames(frames);
				//frames->release();

				CCAnimationCache::sharedAnimationCache()->addAnimation(panim, name.c_str());
			}
		}
	}

	if (pnode->hasNode("settings"))
	{
		_settings.initialize(pnode->firstnode("settings"));
	}

	CCSize sizeCollid = CCSize(1, 1);
	if (_settings.hasKey("collid"))
	{
		sizeCollid = _settings.getSize("collid");
	}

	pnodes = pnode->nodes("pose");
	CCARRAY_FOREACH(pnodes, pnodeobj)
	{
		GUARD_FIELD(loadposes);

		CAXMLNode* psub = (CAXMLNode*)pnodeobj;
		//<pose name="burn" anim="player_swim">
		CASpriteModelPose* ppose = new CASpriteModelPose();
		ppose->setCollid(sizeCollid);
		ppose->initialize(psub);
		_poses->setObject(ppose, ppose->name().c_str());
		ppose->release();
	}

	_Trace("model %s/%s loaded", list.c_str(), tex.c_str());

	_src = list;
	_tex = tex;
}

void CASpriteModel::build(const char* pszFile)
{
	GUARD_FUNCTION();

	CAXMLNode* pnode = CAXML::parse(pszFile, true);
	_Assert(null != pnode);

	pnode = pnode->firstnode("sprite");
	_Assert(null != pnode);
	
	initialize(pnode);

	_file = pszFile;

	return;
}

bool CASpriteModel::hasPose(const CAString& pose)
{
	CASpriteModelPose* ppose = (CASpriteModelPose*)_poses->objectForKey(pose);
	return null != ppose;
}

CASpriteModelPose* CASpriteModel::getPose(const CAString& pose)
{
	CASpriteModelPose* ppose = (CASpriteModelPose*)_poses->objectForKey(pose);
	_Assert(ppose);
	return ppose;
}
